JavaScript 与 HTML 的交互是通过事件实现的,可以使用仅在事件发生时执行的监听器订阅事件。
IE 事件流被称为事件冒泡,事件被定义为从最具体的元素开始触 发,然后向上传播,例如如下 HTML 页面:
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
在点击页面中的
Netscape 团队提出了另一种名为事件捕获的事件流,最不具体的节点最先收到事件,而最具体的节点最后收到事件。如上页面中,click 事件会按照 document -> ->
->DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
以下介绍几种事件绑定方式:
// 方式1
<input type="button" value="Click Me" onclick="console.log('Clicked')" />
// 方式2
<script>
function showMessage() {
console.log("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
// 方式3
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log("Clicked");
};
// 方式4
// 接收 3 个参数:事件名、事件处理函 数和一个布尔值
// true 表示在捕获阶段调用事件处理程序
// false (默认值)表示在冒泡阶段调用事件处理程序
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
// 移除事件
btn.removeEventListener("click", function() {
console.log(this.id);
}, false);
// IE8 及之前版本
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
console.log("Clicked");
});
// 移除事件
btn.detachEvent("onclick", function() {
console.log("Clicked");
})
event 对象是传给事件处理程序的唯一参数,其包含与特定事件相关的属性和方法,不同的事件生成的事件对象也会包含不同 的属性和方法。下表记录了事件对象的公共属性和方法:
属性/方法 | 类型 | 说明 |
---|---|---|
bubbles | 布尔值 | 事件是否冒泡 |
cancelable | 布尔值 | 是否可以取消事件的默认行为 |
currentTarget | 元素 | 当前事件处理程序所在的元素 |
defaultPrevented | 布尔值 | true 表示已经调用 preventDefault() 方法 |
detail | 整数 | 事件相关的其他信息 |
eventPhase | 整数 | 调用事件处理程序的阶段:1 代表捕获阶段,2 代表到达目标,3 代表冒泡阶段 |
preventDefault() | 函数 | 取消事件的默认行为,cancelable 为 true 时可用 |
stopImmediatePropagation() | 函数 | 取消所有后续事件捕获或事件冒泡 |
stopPropagation() | 函数 | 取消所有后续事件捕获或事件冒泡,bubbles 为 true 时可用 |
target | 元素 | 事件目标 |
trusted | 布尔值 | true 表示事件是由浏览器生成的。false 表示事件是开 发者通过 JS 创建的 |
type | 字符串 | 被触发的事件类型 |
View | AbstractView | 与事件相关的抽象视图,等于事件所发生的 window 对象 |
DOM3 Events 定义了如下事件类型:
用户界面事件或 UI 事件不一定跟用户操作有关,主要有以下几种:
load 事件
window.addEventListener("load", (event) => {
console.log("Loaded!");
});
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload="console.log('Loaded!')">
</body>
</html>
unload 事件
window.addEventListener("unload", (event) => {
console.log("Unloaded!");
});
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onunload="console.log('Unloaded!')">
</body>
</html>
resize 事件
window.addEventListener("resize", (event) => {
console.log("Resized");
});
scroll 事件
window.addEventListener("scroll", (event) => {
if (document.compatMode == "CSS1Compat") {
console.log(document.documentElement.scrollTop);
} else {
console.log(document.body.scrollTop);
}
});
焦点事件在页面元素获得或失去焦点时触发,焦点事件有以下几种:
当焦点从页面中的一个元素移到另一个元素上时,会依次发生如下事件:
DOM3 定义了 9 种鼠标事件:
除了 mouseenter 和 mouseleave,所有的鼠标事件都会冒泡,都可以被取消。鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在 event 对象的clientX 和 clientY 属性中。可以通过下面的方式获取鼠标事件的客户端坐标:
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Client coordinates: ${event.clientX}, ${event.clientY}`);
});
页面坐标是事件发生时鼠标光标在页面上的坐标,通过 event 对象的 pageX 和 pageY 可以获取。这两个属性表示鼠标光标在页面上的位置,因此反映的是光标到页面而非视口左边与上边的距离。
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Page coordinates: ${event.pageX}, ${event.pageY}`);
});
鼠标事件不仅是在浏览器窗口中发生的,也是在整个屏幕上发生的。可以通过 event 对象的 screenX 和 screenY 属性获取鼠标光标在屏幕上的坐标。
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
console.log(`Screen coordinates: ${event.screenX}, ${event.screenY}`);
});
键盘上的修饰键 Shift、Ctrl、Alt 和 Meta 经常用于修改鼠标事件的行为。DOM 规定了 4 个属性来表示这几个修饰键的状态:shiftKey、ctrlKey、altKey 和 metaKey。这几个属性会在各自对应的修饰键被按下时包含布尔值 true,没有被按下时包含 false。
let div = document.getElementById("myDiv");
div.addEventListener("click", (event) => {
let keys = new Array();
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
console.log("Keys: " + keys.join(","));
});
对 mousedown 和 mouseup 事件来说,event 对象上会有一个 button 属性,表示按下或释放的是哪个按键。0 表示鼠标主键、1 表示鼠标中键(通常是滚轮键)、2 表示鼠标副键。
mousewheel 事件会在用户使用鼠标滚轮时触发,包括在垂直方向上任意滚动。这个事件会在任何元素上触发,并冒泡到 document 和 window。mousewheel 事件的 event 对象除了包含鼠标事件的所有标准信息之外,还有一个名为 wheelDelta 的新属性。当鼠标滚轮向前滚动时,wheelDelta 每次都是+120;而当鼠标滚轮向后滚动时,wheelDelta 每次都是–120。
document.addEventListener("mousewheel", (event) => {
console.log(event.wheelDelta);
});
键盘事件是用户操作键盘时触发的,其包含 3 个事件:
输入事件只有一个,即 textInput。这个事件是对 keypress 事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。textInput 会在文本被插入到文本框之前触发。当用户按下键盘上的某个字符键时,首先会触发 keydown 事件,然后触发 keypress 事件,最后触发 keyup 事件。
对于 keydown 和 keyup 事件,event 对象的 keyCode 属性中会保存一个键码,对应键盘上特定的一个键。
let textbox = document.getElementById("myText");
textbox.addEventListener("keyup", (event) => {
console.log(event.keyCode);
});
浏览器在 event 对象上支持 charCode 属性,只有发生 keypress 事件时这个属性才会被设置值,包含的是按键字符对应的 ASCII 编码。但是 DOM3 Events 规范并未规定 charCode 属性,而是定义了 key 和 char 两个新属性。key 属性用于替代 keyCode,且包含字符串。在按下字符键时,key 的值等于文本字符(如 “k”或“M”);在按下非字符键时,key 的值是键名(如“Shift”或“ArrowDown”)。char 属性在按下字符键时与 key 类似,在按下非字符键时为 null。
DOM3 Events 规范增加了一个名为 textInput 的事件,其在字符被输入到可编辑区域时触发。textInput 事件主要关注字符,所以在 event 对象上提供了一个 data 属性,包含要插入的字符。data 的值始终是要被插入的字符,因此如果在按 S 键时没有按 Shift 键,data 的值就是"s",但在按 S 键时同时按 Shift 键,data 的值则是"S"。
let textbox = document.getElementById("myText");
textbox.addEventListener("textInput", (event) => {
console.log(event.data);
});
contextmenu 事件专门用于表示何时应该显示上下文菜单,从而允许开发者取消默认的上下文菜单并提供自定义菜单。通常自定义的上下文菜单都是通过 oncontextmenu 事件处理程序触发显示,并通过 onclick 事件处理程序 触发隐藏的,例如:
<!DOCTYPE html>
<html>
<head>
<title>ContextMenu Event Example</title>
</head>
<body>
<div id="myDiv">Right click or Ctrl+click me to get a custom context menu. Click anywhere else to get the default context menu.</div>
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color: silver">
<li><a href="http://www.somewhere.com"> somewhere</a></li>
<li><a href="http://www.wrox.com">Wrox site</a></li>
<li><a href="http://www.somewhere-else.com">somewhere-else</a></li>
</ul>
</body>
</html>
上述例子中的
window.addEventListener("load", (event) => {
let div = document.getElementById("myDiv");
div.addEventListener("contextmenu", (event) => {
event.preventDefault();
let menu = document.getElementById("myMenu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.style.visibility = "visible";
});
document.addEventListener("click", (event) => {
document.getElementById("myMenu").style.visibility = "hidden";
});
});
这里在
beforeunload 事件会在 window 上触发,给开发者提供阻止页面被卸载的机会。这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。
DOMContentLoaded 事件会在 DOM 树构建完成后立即触发,而不用等待图片、JavaScript 文件、CSS 文件或其他资源加载完成。相对于 load 事件在页面完全加载后触发,该事件可以让用户能够更快地与页面交互。
readystatechange 事件提供文档或元素加载状态的信息。支持 readystatechange 事件的对象都有一个 readyState 属性,该属性具有一个字符串表示对象的加载状态:
可以像下面这样使用 readystatechange 事件:
document.addEventListener("readystatechange", (event) => {
if (document.readyState == "interactive") {
console.log("Content loaded");
}
});
hashchange 事件用于在 URL 散列值发生变化时通知开发者。onhashchange 事件处理程序必须添加给 window,event 对象有两个新属性:oldURL 和 newURL。这两个属性分别保存变化前后的 URL。
window.addEventListener("hashchange", (event) => {
console.log(`Old URL: ${event.oldURL}, New URL: ${event.newURL}`);
});
// 如果想确定当前的散列值,最好使用 location 对象:
window.addEventListener("hashchange", (event) => {
console.log(`Current hash: ${location.hash}`);
});